package org.wu.framework.lazy.database.datasource.proxy;

import org.wu.framework.core.ReflexUtils;
import org.wu.framework.lazy.database.datasource.proxy.connection.LazyConnectionInvocationHandler;
import org.wu.framework.lazy.database.datasource.proxy.handler.JdbcHandlerAdapter;

import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.Connection;

/**
 * 数据源代理对象
 *
 * @see LazyProxyDataSource
 */
public class LazyProxyDataSourceInvocationHandler implements InvocationHandler {
    private final DataSource targetDataSource;

    // 连接处理器
    private final JdbcHandlerAdapter jdbcHandlerAdapter;

    // sql 审计


    public LazyProxyDataSourceInvocationHandler(DataSource targetDataSource, JdbcHandlerAdapter jdbcHandlerAdapter) {
        this.targetDataSource = targetDataSource;
        this.jdbcHandlerAdapter = jdbcHandlerAdapter;
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy  the proxy instance that the method was invoked on
     * @param method the {@code Method} instance corresponding to
     *               the interface method invoked on the proxy instance.  The declaring
     *               class of the {@code Method} object will be the interface that
     *               the method was declared in, which may be a superinterface of the
     *               proxy interface that the proxy class inherits the method through.
     * @param args   an array of objects containing the values of the
     *               arguments passed in the method invocation on the proxy instance,
     *               or {@code null} if interface method takes no arguments.
     *               Arguments of primitive types are wrapped in instances of the
     *               appropriate primitive wrapper class, such as
     *               {@code java.lang.Integer} or {@code java.lang.Boolean}.
     * @return the value to return from the method invocation on the
     * proxy instance.  If the declared return type of the interface
     * method is a primitive type, then the value returned by
     * this method must be an instance of the corresponding primitive
     * wrapper class; otherwise, it must be a type assignable to the
     * declared return type.  If the value returned by this method is
     * {@code null} and the interface method's return type is
     * primitive, then a {@code NullPointerException} will be
     * thrown by the method invocation on the proxy instance.  If the
     * value returned by this method is otherwise not compatible with
     * the interface method's declared return type as described above,
     * a {@code ClassCastException} will be thrown by the method
     * invocation on the proxy instance.
     * @throws Throwable the exception to throw from the method
     *                   invocation on the proxy instance.  The exception's type must be
     *                   assignable either to any of the exception types declared in the
     *                   {@code throws} clause of the interface method or to the
     *                   unchecked exception types {@code java.lang.RuntimeException}
     *                   or {@code java.lang.Error}.  If a checked exception is
     *                   thrown by this method that is not assignable to any of the
     *                   exception types declared in the {@code throws} clause of
     *                   the interface method, then an
     *                   {@link UndeclaredThrowableException} containing the
     *                   exception that was thrown by this method will be thrown by the
     *                   method invocation on the proxy instance.
     * @see UndeclaredThrowableException
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {

            if (isCloseMethod(method, args)) {
                if (ReflexUtils.existsDeclaredMethod(targetDataSource.getClass(), "connectionClose")) {
                    Method declaredMethod = ReflexUtils.findDeclaredMethod(targetDataSource.getClass(), "connectionClose");

                    ReflexUtils.invokeDeclaredMethod(targetDataSource, declaredMethod);
                }
                return null;
            }


            if (isGetLazyConnectionMethod(method)) {
                return jdbcHandlerAdapter.findFirstDataSourceHandler().getLazyConnection((DataSource) proxy);
            }
            // datasource 获取 Connection 连接处理
            Object invoke = method.invoke(targetDataSource, args);
            if (invoke instanceof Connection) {
                return LazyConnectionInvocationHandler.createProxy(targetDataSource, (Connection) invoke, jdbcHandlerAdapter);
            }
            return invoke;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw throwable;
        }

    }

    /**
     * 是否是关闭数据源方法
     *
     * @param method 方法
     * @return 是否是close 方法
     */
    protected boolean isGetLazyConnectionMethod(Method method) {
        return method.getName().equals("getLazyConnection") && method.getParameterCount()==0;
    }

    /**
     * 是否是关闭数据源方法
     *
     * @param method 方法
     * @param args   参数
     * @return 是否是close 方法
     */
    protected boolean isCloseMethod(Method method, Object[] args) {
        return method.getName().equals("connectionClose");
    }


    /**
     * 静态方法创建数据源代理对象
     *
     * @param dataSource 原始数据源
     * @param <A>        数据源类型
     * @return 数据源代理对象
     */
    public static <A extends DataSource> A createProxy(DataSource dataSource) {
        return createProxy(dataSource, new JdbcHandlerAdapter());
    }

    /**
     * 静态方法创建数据源代理对象
     *
     * @param dataSource            原始数据源
     * @param <A>                   数据源类型
     * @param jdbcHandlerAdapter 连接处理器
     * @return 数据源代理对象
     */
    public static <A extends DataSource> A createProxy(DataSource dataSource,
                                                       JdbcHandlerAdapter jdbcHandlerAdapter) {
        ClassLoader classLoader = LazyProxyDataSource.class.getClassLoader();
        Class<?>[] interfaces = new Class<?>[]{LazyProxyDataSource.class};
        InvocationHandler handler = new LazyProxyDataSourceInvocationHandler(dataSource, jdbcHandlerAdapter);
        return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
}
